﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace SilverFlow.Controls
{
    /// <summary>
    /// Canvas containing floating wndows.
    /// </summary>
    public class FloatingWindowHost : Canvas
    {
        /// <summary>
        /// Thickness of resizing area.
        /// </summary>
        private const double SnapinDistanceDefaultValue = 5.0;

        /// <summary>
        /// Current ZIndex of a child element
        /// </summary>
        private static int zIndex = 1;

        /// <summary>
        /// Taskbar containing minimized windows.
        /// </summary>
        private Taskbar taskBar = new Taskbar();

        #region public bool SnapinEnabled

        /// <summary>
        /// Gets or sets a value indicating whether snap in is enabled.
        /// </summary>
        /// <value><c>true</c> if snap in is enabled; otherwise, <c>false</c>.</value>
        public bool SnapinEnabled
        {
            get { return (bool)GetValue(SnapinEnabledProperty); }
            set { SetValue(SnapinEnabledProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="FloatingWindow.SnapinEnabled" /> dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the <see cref="FloatingWindow.SnapinEnabled" /> dependency property.
        /// </value>
        public static readonly DependencyProperty SnapinEnabledProperty =
            DependencyProperty.Register(
            "SnapinEnabled",
            typeof(bool),
            typeof(FloatingWindowHost),
            new PropertyMetadata(true, null));

        #endregion

        #region public double SnapinDistance

        /// <summary>
        /// Gets or sets a value of the snap in distance.
        /// </summary>
        /// <value>Snap in distance.</value>
        public double SnapinDistance
        {
            get { return (double)GetValue(SnapinDistanceProperty); }
            set { SetValue(SnapinDistanceProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="FloatingWindowHost.SnapinDistance" /> dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the <see cref="FloatingWindowHost.SnapinDistance" /> dependency property.
        /// </value>
        public static readonly DependencyProperty SnapinDistanceProperty =
            DependencyProperty.Register(
            "SnapinDistance",
            typeof(double),
            typeof(FloatingWindowHost),
            new PropertyMetadata(SnapinDistanceDefaultValue, null));

        #endregion

        #region public double SnapinMargin

        /// <summary>
        /// Gets or sets a value of the snap in margin - distance between adjacent edges.
        /// </summary>
        /// <value>Snap in margin.</value>
        public double SnapinMargin
        {
            get { return (double)GetValue(SnapinMarginProperty); }
            set { SetValue(SnapinMarginProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="FloatingWindowHost.SnapinMargin" /> dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the <see cref="FloatingWindowHost.SnapinMargin" /> dependency property.
        /// </value>
        public static readonly DependencyProperty SnapinMarginProperty =
            DependencyProperty.Register(
            "SnapinMargin",
            typeof(double),
            typeof(FloatingWindowHost),
            new PropertyMetadata(0.0, null));

        #endregion

        #region public bool ShowMinimizedOnlyInTaskbar

        /// <summary>
        /// Gets or sets a value indicating whether to show only minimized windows in the taskbar.
        /// </summary>
        /// <value><c>true</c> if to show only minimized windows in the taskbar; otherwise, <c>false</c>.</value>
        public bool ShowMinimizedOnlyInTaskbar
        {
            get { return (bool)GetValue(ShowMinimizedOnlyInTaskbarProperty); }
            set { SetValue(ShowMinimizedOnlyInTaskbarProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="FloatingWindow.ShowMinimizedOnlyInTaskbarProperty" /> dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the <see cref="FloatingWindow.ShowMinimizedOnlyInTaskbarProperty" /> dependency property.
        /// </value>
        public static readonly DependencyProperty ShowMinimizedOnlyInTaskbarProperty =
            DependencyProperty.Register(
            "ShowMinimizedOnlyInTaskbar",
            typeof(bool),
            typeof(FloatingWindowHost),
            new PropertyMetadata(false, null));

        #endregion

        public FloatingWindowHost()
        {
            this.Children.Add(taskBar);
            this.SizeChanged += new SizeChangedEventHandler(FloatingWindowHost_SizeChanged);
        }

        /// <summary>
        /// Gets the floating windows collection.
        /// </summary>
        /// <value>The floating windows collection.</value>
        public IEnumerable<FloatingWindow> FloatingWindows
        {
            get
            {
                return this.Children.OfType<FloatingWindow>();
            }
        }

        /// <summary>
        /// Adds the specified floating window to the collection of child elements of the canvas.
        /// </summary>
        /// <param name="window">The floating window.</param>
        /// <exception cref="ArgumentNullException">Floating window is null.</exception>
        public void Add(FloatingWindow window)
        {
            if (window == null)
                throw new ArgumentNullException("window");

            if(!this.Children.Contains(window))
                this.Children.Add(window);
        }

        /// <summary>
        /// Removes the specified floating window from the collection of child elements of the canvas.
        /// </summary>
        /// <param name="window">The floating window.</param>
        public void Remove(FloatingWindow window)
        {
            if (window != null)
            {
                this.Children.Remove(window);
            }
        }

        /// <summary>
        /// Gets Snap In bounds as bounds of the host and all open windows except the specified one.
        /// </summary>
        /// <param name="windowToExclude">The window to exclude from the list.</param>
        /// <returns>List of bounding rectangles.</returns>
        public IEnumerable<Rect> GetSnapinBounds(FloatingWindow windowToExclude)
        {
            List<Rect> bounds = new List<Rect>();

            // Add host bounds
            bounds.Add(new Rect(0, 0, ActualWidth, ActualHeight));

            foreach (var window in FloatingWindows)
            {
                if (window != windowToExclude && window.IsOpen)
                    bounds.Add(window.BoundingRectangle);
            }

            return bounds;
        }

        /// <summary>
        /// Update the taskbar - show or hide a bootstrap button if number of windows has changed.
        /// </summary>
        public void UpdateTaskbar()
        {
            int windowsInTaskbar = FloatingWindows.Where(x => 
                x.IsOpen && 
                x.ShowInTaskbar && 
                !(ShowMinimizedOnlyInTaskbar && x.WindowState != WindowState.Minimized)).Count();

                taskBar.ShowBootstrapButton();
        }

        /// <summary>
        /// Sets the topmost window in front of other windows.
        /// </summary>
        public void SetTopmostWindow()
        {
            FloatingWindows
                .Where(x => x.IsOpen && x.TopMost).ToList()
                .ForEach(x => FloatingWindowHost.SetTopmost(x));

            SetTopmostTaskbar();
        }

        /// <summary>
        /// Sets the specified floating window topmost.
        /// </summary>
        /// <param name="window">The floating window.</param>
        /// <exception cref="ArgumentNullException">Floating window is null.</exception>
        public static void SetTopmost(UIElement window)
        {
            if (window == null)
                throw new ArgumentNullException("window");

            zIndex++;
            Canvas.SetZIndex(window, zIndex);
        }

        /// <summary>
        /// Sets the taskbar in front of other windows.
        /// </summary>
        private void SetTopmostTaskbar()
        {
            zIndex++;
            Canvas.SetZIndex(taskBar, zIndex);
        }

        /// <summary>
        /// Updates clipping region on host SizeChanged event.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.SizeChangedEventArgs"/> instance containing the event data.</param>
        private void FloatingWindowHost_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Clip = new RectangleGeometry()
            {
                Rect = new Rect(0, 0, ActualWidth, ActualHeight)
            };
        }
    }
}
